package com.example.dishit.model.dbproxy;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import com.example.dishit.controller.main.MainController;
import com.example.dishit.model.dbproxy.data.Dish;
import com.example.dishit.model.dbproxy.data.DishTranslator;
import com.example.dishit.model.dish.DishData;

public class DBProxy implements IDBProxy {
	private static final String DISH_FAILED_STR = "Error";

	// FIXME: Change these to "success" strings instead of "failed" ones.
	//private static final String CREATE_DISH_FAILED_STR = "Adding Restaurant Failed";
	private static final String FRIEND_MAINT_FAILED_STR = "not a registered user";
	
	// FIXME: This is a hack. This should be in the Database schema instead.
	private static final Map<String,DishData> restaurantNameToImageUrls = new HashMap<String,DishData>();

	/**
	 * (DBProxyUtils must be called from a single thread, otherwise this error
	 * can occur... "Make sure to release the connection before allocating
	 * another one. Invalid use of SingleClientConnManager: connection still
	 * allocated.")
	 */
	private static final Executor dbProxyUtilsThread = Executors
			.newSingleThreadExecutor();

	// ==============================================================
	// INCOMING DISHES

	public void storeReceivedDish(final String myUserId, final DishData data,
			final IDBProxyCallback callback) {
		dbProxyUtilsThread.execute(new Runnable() {
			public void run() {
				final String result = storeReceivedDish(myUserId, data);

				final boolean success = !result.isEmpty()
						&& !result.startsWith(DISH_FAILED_STR);

				callback.onResultCallback(success, result);
			}
		});
	}

	private static String storeReceivedDish(String myUserId, DishData data) {
		final Dish dish = DishTranslator.translate(data);
		dish.setDisher(MainController.THIS_USER_ID);

        // FIXME: hack; using in-memory stuff b/c we don't keep this in the DB.
		restaurantNameToImageUrls.put(dish.getReview().getRestaurant().getName(), data);
		
		return DBProxyUtils.storeReceivedDish(dish, null);
	}

	/**
	 * Asynchronous. Get the dishes that my friends (a.k.a. "Dishers") sent out.
	 */
	public void getLatestReceivedDishes(final String myUserId,
			final long afterThisTimeMillis, final IDBProxyCallback callback) {
		dbProxyUtilsThread.execute(new Runnable() {
			public void run() {
				final List<DishData> result = getLatestReceivedDishes(myUserId,
						afterThisTimeMillis);
				callback.onDishDataCallback(result);
			}
		});
	}

	private static List<DishData> getLatestReceivedDishes(
			final String myUserId, final long afterThisTimeMillis) {
		final List<Dish> dishes = DBProxyUtils.getReceivedDishes(myUserId);

		// Convert the Dishes to DishDatas.
		final List<DishData> dishDatas = new ArrayList<DishData>();
		for (final Dish dish : dishes) {
		    final List<String> imageUrls = new ArrayList<String>();
		    
		    final DishData dbData = DishTranslator.translate(dish);
		    if(dbData.getImageUrls() != null) imageUrls.addAll(dbData.getImageUrls());
		    
		    final DishData.Builder builder = new DishData.Builder();
		    builder.dishData(dbData);
		    
		    // FIXME: hack; using in-memory stuff b/c we don't keep this in the DB.
			final DishData cachedData = restaurantNameToImageUrls.get(dish.getReview().getRestaurant().getName());
			if(cachedData != null) {
			    builder.dishData(cachedData);
			    if(cachedData.getImageUrls() != null) imageUrls.addAll(cachedData.getImageUrls());
			}
			
			builder.imageUrls(imageUrls);
			dishDatas.add(builder.build());
		}

		return dishDatas;
	}

	// ==============================================================
	// OUTGOING DISHES

	/** Asynchronous. */
	public void broadcastDish(final DishData data,
			final IDBProxyCallback callback) {
		dbProxyUtilsThread.execute(new Runnable() {
			public void run() {
				final String result = broadcastDish(data);

				final boolean success = !result.isEmpty()
						&& !result.startsWith(DISH_FAILED_STR);

				callback.onResultCallback(success, result);
			}
		});
	}

	private static String broadcastDish(DishData data) {
		final Dish dish = DishTranslator.translate(data);
		
		// FIXME: hack; using in-memory stuff b/c we don't keep this in the DB.
        restaurantNameToImageUrls.put(dish.getReview().getRestaurant().getName(), data);

		final List<byte[]> pngImages = data.getPngImages();
		final byte[] image = (pngImages != null && !pngImages.isEmpty()) ? pngImages
				.get(0) : null;
		return DBProxyUtils.broadcastDish(dish, image);
	}

	/** Asynchronous. Get the dishes that I sent out. */
	public void getLatestSentDishes(final String myUserId,
			final long afterThisTimeMillis, final IDBProxyCallback callback) {
		dbProxyUtilsThread.execute(new Runnable() {
			public void run() {
				final List<DishData> result = getLatestSentDishes(myUserId,
						afterThisTimeMillis);
				callback.onDishDataCallback(result);
			}
		});
	}

	private static List<DishData> getLatestSentDishes(final String myUserId,
			final long afterThisTimeMillis) {
		final List<Dish> dishes = DBProxyUtils.getSentDishes(myUserId);

		// Convert Dishes to DishDatas.
		final List<DishData> dishDatas = new ArrayList<DishData>();
		for (final Dish dish : dishes) {
		    final List<String> imageUrls = new ArrayList<String>();
		    
			final DishData dbData = DishTranslator.translate(dish);
			if(dbData.getImageUrls() != null) imageUrls.addAll(dbData.getImageUrls());
            
            final DishData.Builder builder = new DishData.Builder();
            builder.dishData(dbData);
            
            // FIXME: hack; using in-memory stuff b/c we don't keep this in the DB.
            final DishData cachedData = restaurantNameToImageUrls.get(dish.getReview().getRestaurant().getName());
            if(cachedData != null) {
                builder.dishData(cachedData);
                if(cachedData.getImageUrls() != null) imageUrls.addAll(cachedData.getImageUrls());
            }
            
            builder.imageUrls(imageUrls);
            dishDatas.add(builder.build());
		}

		return dishDatas;
	}

	// ==============================================================
	// DISHER MAINTENANCE

	/** Asynchronous. */
	public void getMyDishers(final String myUserId,
			final IDBProxyCallback callback) {
		dbProxyUtilsThread.execute(new Runnable() {
			public void run() {
				final List<String> result = getMyDishers(myUserId);
				callback.onDishersCallback(result);
			}
		});
	}

	private static List<String> getMyDishers(final String myUserId) {
		return DBProxyUtils.getFriends(myUserId);
	}

	/** Asynchronous. */
	public void followerUser(final String thisUserId,
			final String userIdToFollow, final IDBProxyCallback callback) {
		dbProxyUtilsThread.execute(new Runnable() {
			public void run() {
				final String result = followerUser(thisUserId, userIdToFollow);

				final boolean success = !result
						.contains(FRIEND_MAINT_FAILED_STR) && !result.isEmpty();
				callback.onResultCallback(success, result);
			}
		});
	}

	private static String followerUser(final String thisUserId,
			final String userIdToFollow) {
		return DBProxyUtils.addFriend(thisUserId, userIdToFollow);
	}

	/** Asynchronous. */
	public void unfollowUser(final String thisUserId,
			final String userIdToUnfollow, final IDBProxyCallback callback) {
		dbProxyUtilsThread.execute(new Runnable() {
			public void run() {
				final String result = unfollowUser(thisUserId, userIdToUnfollow);

				final boolean success = !result
						.contains(FRIEND_MAINT_FAILED_STR) && !result.isEmpty();
				callback.onResultCallback(success, result);
			}
		});
	}

	private static String unfollowUser(final String thisUserId,
			final String userIdToUnfollow) {
		return DBProxyUtils.removeFriend(thisUserId, userIdToUnfollow);
	}
}
